﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography.Xml;
using System.Security.Cryptography;
using Microsoft.Cryptography;
using Microsoft.Cryptography.BouncyCastle;

namespace Microsoft.Cryptography.Samples.XmlSignature
{
    class Program
    {
        const string PLAIN_XML = "<?xml version=\"1.0\" encoding=\"utf-16\" ?><root><Test Id = \"NodeToSign\">Hello World {0} </Test></root>";

        private static void ExecuteTest(string certificateName, string password, string algorithmName)
        {
            try
            {
                X509Certificate2 sigCert = new X509Certificate2(certificateName,
                    password, X509KeyStorageFlags.Exportable);

                //Sign:
                string signedXml = SignXml(string.Format(PLAIN_XML, Guid.NewGuid().ToString()), sigCert, algorithmName);
                Console.WriteLine("Signature created. SignedXml: {0} ", signedXml);

                //Verify:
                bool check = CheckSignature(signedXml, sigCert);
                Console.WriteLine("Check: " + check.ToString());
                if (check == false)
                {
                    Console.WriteLine("!!! INVALID SIGNATURE !!!");
                }

                Console.WriteLine("Done.");
            }
            catch (Exception err)
            {
                Console.WriteLine("EXCEPTION: {0}", err);
                throw;
            }
        }

        private static string SignXml(string plainxmlToSign, X509Certificate2 sigCert, string signatureMethod)
        {
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(plainxmlToSign);

            SignedXml xmlToSign = new SignedXml(doc);
            xmlToSign.SigningKey = sigCert.PrivateKey;
            xmlToSign.SignedInfo.SignatureMethod = signatureMethod;
            Reference reference = new Reference();
            reference.Uri = "#NodeToSign";
            XmlDsigC14NTransform transform = new XmlDsigC14NTransform();
            reference.AddTransform(transform);
            xmlToSign.AddReference(reference);
            xmlToSign.ComputeSignature();
            XmlElement xmlDigitalSignature = xmlToSign.GetXml();

            doc.DocumentElement.AppendChild(xmlDigitalSignature);

            StringBuilder signedXml = new StringBuilder();

            XmlTextWriter xmltw = new XmlTextWriter(new System.IO.StringWriter(signedXml));
            doc.WriteTo(xmltw);
            xmltw.Flush();
            xmltw.Close();

            return signedXml.ToString();
        }

        private static bool CheckSignature(string signedXmlText, X509Certificate2 sigCert)
        {
            XmlDocument xmlDocument = new XmlDocument();
            xmlDocument.LoadXml(signedXmlText);

            SignedXml signedXml = new SignedXml(xmlDocument);
            XmlNodeList nodeList = xmlDocument.GetElementsByTagName("Signature");
            signedXml.LoadXml((XmlElement)nodeList[0]);

            object dummy = CryptoConfig.CreateFromName("RSABase");

            if (!(dummy is RSABase))
            {
                throw new Exception("The RSA-class configured in machine.config must derive from Microsoft.Cryptography.RSABase!");
            }

            RSABase rsa = (RSABase)dummy;
            rsa.ImportParameters(((RSA)sigCert.PublicKey.Key).ExportParameters(false));

            return signedXml.CheckSignature(rsa);
        }

        static void ShowUsage()
        {
            Console.WriteLine("Wrong syntax. Correct Syntax: 'Microsoft.Cryptography.Samlples.XmlSignature <CERTIFICATEFILENAME> <PASSWORD>'");
            Console.WriteLine();
        }

        static void Main(string[] args)
        {
            try
            {
                if (args == null || args.Length == 0)
                {
                    ShowUsage();

                    Console.WriteLine("Using default parameters (CertFilename = 'TestUser01.pfx', Password = 'anfang?01')");
                    Console.WriteLine();

                    args = new string[2] { "TestUser01.pfx", "anfang?01" };
                }

                if (args.Length != 2)
                {
                    ShowUsage();
                    return;
                }

                // These lines are only created to ensure that Visual Studio 
                //copies the referenced libraries to make it easier for developers
                // BEGIN of dummy code
                RSAManaged rsaManaged = new RSAManaged();
                rsaManaged.Clear();
                RSABase rsaBase = (RSABase)rsaManaged;
                rsaBase.Clear();
                RSABouncyCastle rsaBouncyCastle = new RSABouncyCastle();
                rsaBouncyCastle.Clear();
                // END of dummy code

                ExecuteTest(args[0], args[1], "http://www.w3.org/2000/09/xmldsig#rsa-sha1");
                ExecuteTest(args[0], args[1], "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256");
                ExecuteTest(args[0], args[1], "http://www.w3.org/2000/09/xmldsig#rsa-sha256");
                ExecuteTest(args[0], args[1], "http://www.w3.org/2000/09/xmldsig#rsa-sha384");
                ExecuteTest(args[0], args[1], "http://www.w3.org/2000/09/xmldsig#rsa-sha512");
                ExecuteTest(args[0], args[1], "http://www.w3.org/2000/09/xmldsig#rsa-md5");
                ExecuteTest(args[0], args[1], "http://www.w3.org/2000/09/xmldsig#rsa-ripemd160");

                Console.WriteLine("!!! COMPLETED SUCCESSFULLY !!!");
            }
            catch (Exception error)
            {
                Console.WriteLine("EXCEPTION OCCURED: {0}",
                    error.ToString());
            }
            finally
            {
                Console.WriteLine();
                Console.WriteLine("Press any key to quit program...");
                Console.ReadKey();
            }
        }
    }
}
